# -*- coding: utf-8 -*-
"""
Created on Wed Jul  3 09:05:20 2019

@author: frankwin7
"""
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import datetime  # For datetime objects
import pandas as pd
#import statistics
#import math
# Import the backtrader platform
import backtrader as bt
#import pysnooper
from functools import lru_cache
import time
import warnings
warnings.filterwarnings('ignore')
#import numpy as np
import empyrical as emp
#import sys
#import matplotlib.pyplot as plt
#import pyfolio as pf
#sys.path.append('E:\\MyProjects\\pyfolio')#G:\\zwPython\\zwrk\\\pyfolio
# silence warnings

#import pandas_datareader.data as web
#from numpy import *



"""
class diff_pct_close_ema_ind(bt.Indicator):
    lines = ('dpce',)
    params = (('emaperiod', 750),)
              #)
            #('upper', 3.0),
             # ('lower', -2.7),
              
    plotinfo = dict(plotymargin = 0, 
                    plothlines=[-2.7, 3]
                    )
              
    #@pysnooper.snoop()
    def _plotlabel(self):
        plabels = [self.p.emaperiod]
        return plabels

    #def _plotinit(self):
        #self.plotinfo.plotyhlines += [self.p.lowerband, self.p.upperband]
    #@pysnooper.snoop()
    def __init__(self):
        #self.addminperiod(self.p.emaperiod)
        # Add a MovingAverageSimple indicator
        self.ema = bt.indicators.ExponentialMovingAverage(
                self.datas[0], period=self.p.emaperiod)
        self.lines.dpce = (self.datas[0].close / self.ema - 1) * 100

    #def next(self):
        

    #class mean_ind(_diff_pct_close_ema_ind):
    lines = ('mean',)
    #@pysnooper.snoop()
    def __init__(self):
        super().__init__()
        self.lines.mean = bt.indicators.SMA(self.lines.dpce, period = self.params.meanperiod)
        self.stdev = bt.indicators.StdDev(self.lines.dpce, period = self.params.meanperiod)

    #class negative_abnormal_ind(mean_ind):
    lines = ('lower',)
    #@pysnooper.snoop()
    def __init__(self):
        super().__init__()
        self.lines.lower = self.lines.mean - self.params.deviation_degree * self.stdev

    #def next(self):
        #        super(negative_abnormal_ind, self).next()

    #class positive_abnormal_ind(mean_ind):
    lines = ('upper',)
   # @pysnooper.snoop()
    def __init__(self):
        super().__init__()
        self.lines.upper = self.lines.mean + self.params.deviation_degree * self.stdev
        
    #def next(self):
        #        super(positive_abnormal_ind, self).next()
 
    #class UpperMeanLower(_diff_pct_close_ema_ind):
    alias = ('MeanReversionIndicator',)
""" 
# Create a Stratey
@lru_cache(maxsize=128, typed=True)
class EmaMeanReversionStrategy(bt.Strategy):
    params = (
        ('emaperiod', 750),
        ('printlog', False),
        ('lower', - 27),
        ('upper', 30),
        )   
        

       #('meanperiod', 43200),
       # ('deviation_degree', 2.3),
        
    #@pysnooper.snoop()
    def log(self, txt, dt=None, doprint=False):
        ''' Logging function for this strategy'''
        if self.params.printlog or doprint:
            dt = dt or self.datas[0].datetime.datetime(0)
            print('%s, %s' % (dt.isoformat(), txt))
    #@pysnooper.snoop()
    def __init__(self):
        
        # To keep track of pending orders and buy price/commission
        self.order = None
        #self.ordervalue = None
        #self.buycomm = None
        #self.isstoploss = False
        self.lower = - 2.7
        self.upper = 3
        self.dataclose = self.datas[0].close
        self.ema = bt.indicators.ExponentialMovingAverage(
                self.datas[0], period=self.params.emaperiod)
                                #meanperiod = self.params.meanperiod,
                             #deviation_degree = self.params.deviation_degree,
        #self.diff_pct_close_ema_ind = diff_pct_close_ema_ind(emaperiod = self.p.emaperiod)
                                        #upper = self.p.upper, 
                                        #lower = self.p.lower, 
        self.dpce = (self.dataclose / self.ema - 1) * 100
                                                         
        #self.upper = bt.indicators.SimpleMovingAverage(self.params.upper, period=1, subplot = True)
        #self.lower = bt.indicators.SimpleMovingAverage(self.params.lower, period=1, subplot = True)
    #@pysnooper.snoop()
    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        
        if order.status in [order.Completed]:
            if order.isbuy():
               
                self.log(
                    'BUY EXECUTED, Price: %.2f, Size: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     self.position.size,
                     order.executed.value,
                     order.executed.comm))
                
               # self.isbuyorder = True
               # self.ordervalue = order.executed.value
                #self.buycomm = order.executed.comm
            else:  # Sell
               
                self.log('SELL EXECUTED, Price: %.2f, Size: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          self.position.size,
                          order.executed.value,
                          order.executed.comm))
                
                #self.isbuyorder = False
               # self.ordervalue = order.executed.value
            #self.bar_executed = len(self)

        elif order.status in [order.Rejected]:
            self.log('Order Rejected')
        elif order.status in [order.Margin]:
            self.log('Order Margin')
        elif order.status in [order.Canceled]:
            self.log('Order Canceled')
        
        # Write down: no pending order
        self.order = None
    #@pysnooper.snoop()
    def notify_trade(self, trade):
        if not trade.isclosed:
            return
        
        self.log('OPERATION PROFIT, GROSS: %.2f, NET: %.2f' %
                 (trade.pnl, trade.pnlcomm))
        
       # if trade.pnl / self.ordervalue < - 0.08:
         #   self.isstoploss = True
    #@pysnooper.snoop()
    def next(self):
         # Simply log the closing price of the series from the reference
        #self.log('Close, %.2f' % self.dataclose[0])
        
        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return
        # check trade signal
      #  self.buy_sig = bool(self.UpperMeanLower.lines.dpce[0] < self.UpperMeanLower.lines.lower[0])
       # self.sell_sig = bool(self.UpperMeanLower.lines.upper[0] < self.UpperMeanLower.lines.dpce[0])
        #self.buy_sig = bool(self.diff_pct_close_ema_ind.lines.dpce[0] < self.params.lower)
        #self.sell_sig = bool(self.params.upper < self.diff_pct_close_ema_ind.lines.dpce[0])
        self.buy_sig = bool(self.dpce[0] < self.params.lower / 10)
        self.sell_sig = bool(self.params.upper / 10 < self.dpce[0])
        #self.close_sig = bool((self.position.size > 0 and self.UpperMeanLower.lines.mean[0] < self.UpperMeanLower.lines.dpce[0]) or (self.position.size < 0 and self.UpperMeanLower.lines.dpce[0] < self.UpperMeanLower.lines.mean[0]))

        # if math.isnan(self.UpperMeanLower.lines.dpce[0]) or math.isnan(self.UpperMeanLower.lines.upper[0]): return
        if not self.position:
            if self.buy_sig: #and not self.isstoploss
                # BUY, BUY, BUY!!! (with all possible default parameters)
                
                self.log('BUY CREATE, Price: %.2f ' % 
                         (self.dataclose[0])
                    )
                
                # Keep track of the created order to avoid a 2nd order
                self.order = self.buy()
             #   self.order = self.sell(exectype = bt.Order.StopTrail, trailpercent = 0.08)
               # self.order = self.buy(exectype = bt.Order.StopTrail, trailpercent = 0.08)
            elif self.sell_sig:# and not self.isstoploss
                # SELL, SELL, SELL!!! (with all possible default parameters)
                #lenth of position: %d,
                
                self.log('SELL CREATE, Price: %.2f' %
                         (self.dataclose[0]
                          )
                     )
                
                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()
               # self.order = self.buy(exectype = bt.Order.StopTrail, trailpercent = 0.08)
               # self.order = self.sell(exectype = bt.Order.StopTrail, trailpercent = 0.08)
        else:
            if self.position.size < 0 and self.buy_sig:
                # CLOSE (with all possible default parameters)
                
                self.log('CLOSE CREATE, Price:%.2f' %
                         (self.dataclose[0]
                          )
                     )
                
                # Keep track of the created order to avoid a 2nd order
                self.order = self.close()
            elif self.position.size > 0 and self.sell_sig:
                # CLOSE (with all possible default parameters)
                
                self.log('CLOSE CREATE, Price:%.2f' %
                         (self.dataclose[0]
                          )
                     )
                
                # Keep track of the created order to avoid a 2nd order
                self.order = self.close()
                
    #@pysnooper.snoop()
    def stop(self):
        self.log('EMA Period: %d, upper: %d, lower: %d, Ending Value %.2f' %(self.params.emaperiod, self.params.upper, self.params.lower, self.broker.getvalue()), doprint=True)
                  
                   #, deviation_degree: %.2f
                  
                  #self.params.deviation_degree, 
                  
        #self.log('strategy MaxDrawDown: %.2f' % self.stats.drawdown.maxdrawdown[-1])
        
if __name__ == '__main__':
    start = time.process_time()

    # Create a cerebro entity
    cerebro = bt.Cerebro()#optreturn=False
    # Add a strategy
    '''   
    # optimize strategy
    strats = cerebro.optstrategy(
            EmaMeanReversionStrategy,
            emaperiod = range(745, 755)) #2.2, 2.4, 2.6, 2.8, 3, 3.2, 3.4, 3.6, 3.8,
    '''
    cerebro.addobserver(bt.observers.DrawDown)
    # Create a Data Feed
    # 本地数据
    # parase_dates = True是为了读取csv为dataframe的时候能够自动识别datetime格式的字符串，big作为index
    # 注意，这里最后的pandas要符合backtrader的要求的格式
    dataframe = pd.read_csv('bitfinex_BTCUSD_min_20180601_20190531.csv', index_col=0, parse_dates=True)
    dataframe['openinterest'] = 0
    fromdate = datetime.datetime(2019, 4, 1, 0, 1)
    fromdate_str = fromdate.strftime('%Y/%m/%d')
    todate = datetime.datetime(2019, 5, 31, 23, 59)
    todate_str = todate.strftime('%Y/%m/%d')
    data = bt.feeds.PandasData(dataname=dataframe,
        fromdate = fromdate,
        todate = todate,
        timeframe = bt.TimeFrame.Minutes
        )
    # Add the Data Feed to Cerebro
    cerebro.adddata(data, name = 'BTCUSD')
    # Set our desired cash start
    cerebro.broker.setcash(1000.0)
    cerebro.addsizer(bt.sizers.PercentSizer, percents = 95)
    # Set the commission
    cerebro.broker.setcommission(commission=0.0035)

    # At this point tables and chart will show up
    # If no name has been specified, the name is the class name lowercased
    
    cerebro.addanalyzer(bt.analyzers.TimeReturn,  _name='TimeReturn', timeframe=bt.TimeFrame.Days)
    cerebro.addanalyzer(bt.analyzers.TimeReturn, data = data, _name='DataTimeReturn', timeframe=bt.TimeFrame.Days)
    '''
    
    cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
    cerebro.addanalyzer(bt.analyzers.PeriodStats,  _name='PeriodStats', timeframe = bt.TimeFrame.Days)
    cerebro.addanalyzer(bt.analyzers.SQN,  _name='SQN')
    cerebro.addanalyzer(bt.analyzers.TimeReturn,  _name='MinuteTimeReturn', timeframe=bt.TimeFrame.Minutes)
    cerebro.addanalyzer(bt.analyzers.TimeReturn, data = data, _name='DataMinuteTimeReturn', timeframe=bt.TimeFrame.Minutes)
    # At this point tables and chart will show up
    # If no name has been specified, the name is the class name lowercased
    print('年化收益率:策略:', emp.annual_return(strat_daily_return_s, period='daily'), '比特币:', emp.annual_return(data_daily_return_s, period='daily'))
    print('年化波动率:策略:', emp.annual_volatility(strat_daily_return_s, period='daily'), '比特币:', emp.annual_volatility(data_daily_return_s, period='daily'))
    print('最大回撤:策略:', emp.max_drawdown(strat_daily_return_s), '比特币:', emp.max_drawdown(data_daily_return_s))
    print('年化夏普比率:策略:', emp.sharpe_ratio(strat_daily_return_s, risk_free = 0.01 / 365, period='daily'), '比特币:', emp.sharpe_ratio(data_daily_return_s, risk_free = 0.01 / 365, period='daily'))
    print('收益稳定性:策略:', emp.stability_of_timeseries(strat_daily_return_s), '比特币:', emp.stability_of_timeseries(data_daily_return_s))
    print('策略每日统计数据:', strat0.analyzers.PeriodStats.get_analysis())
    print('策略SQN指数:', strat0.analyzers.SQN.get_analysis()['sqn'])
    MinuteTimeReturn = strat0.analyzers.MinuteTimeReturn.get_analysis()
    DataMinuteTimeReturn = strat0.analyzers.DataMinuteTimeReturn.get_analysis()
    MinuteTimeReturn_values = [i for i in MinuteTimeReturn.values()]
    MinuteTimeReturn_datetime = [i for i in MinuteTimeReturn.keys()]
    DataMinuteTimeReturn_values = [i for i in DataMinuteTimeReturn.values()]
    strategy_capital_line = np.cumprod(1 + np.array(MinuteTimeReturn_values))
    data_capital_line = np.cumprod(1 + np.array(DataMinuteTimeReturn_values))
    strategy_capital_line_df = pd.DataFrame(strategy_capital_line)
    strategy_capital_line_df.columns = ['策略累计回报']
    data_capital_line_df = pd.DataFrame(data_capital_line)
    data_capital_line_df.columns = ['比特币累计回报']
    #strategy_capital_line_df['MinuteTimeReturn_datetime'] = pd.to_datetime(strategy_capital_line_df['MinuteTimeReturn_date_str'], format = '%Y%m%d %H:%M')
    MinuteTimeReturn_datetime_df = pd.DataFrame(MinuteTimeReturn_datetime)
    MinuteTimeReturn_datetime_df.columns = ['datetime']
    #data_capital_line_df['MinuteTimeReturn_datetime'] = pd.to_datetime(data_capital_line_df['MinuteTimeReturn_date_str'], format = '%Y%m%d %H:%M')
    strategy_and_data_capital_line = pd.merge(strategy_capital_line_df, data_capital_line_df ,left_index = True, right_index = True)
    strategy_and_data_capital_line_datetime = pd.merge(strategy_and_data_capital_line, MinuteTimeReturn_datetime_df, left_index = True, right_index = True)
    strategy_and_data_capital_line_datetime.set_index("datetime", drop = True, inplace = True)
    strategy_and_data_capital_line_datetime.plot(figsize=(16,9))
    plt.rcParams['font.sans-serif'] = ['SimHei'] #解决中文显示
    plt.legend()
    plt.show()
    cerebro.plot()
    # pyfolio showtime
    
    pf.create_full_tear_sheet(
            returns,
            positions=positions,
            transactions=transactions,
            #gross_lev=gross_lev,
            live_start_date='2019-04-01', # This date is sample specific
            #round_trips=True
            )
    
    #pf.create_bayesian_tear_sheet(returns, benchmark_rets = benchmark_rets, live_start_date='2019-04-01')
    
    '''
    strat_dict ={'emaperiod':[], 'upper':[], 'lower':[], '策略年化索丁诺比率':[], '比特币年化索丁诺比率':[]}
    cerebro.addstrategy(EmaMeanReversionStrategy)

        # Run over everything
        #cerebro.run()
    
    results = cerebro.run()
    print('==================================================')
#    for result in results:
    print('**************************************************')
    for strat in results:
        strat_daily_return = strat.analyzers.TimeReturn.get_analysis()
        data_daily_return = strat.analyzers.DataTimeReturn.get_analysis()
        strat_daily_return_s = pd.Series(strat_daily_return)
        data_daily_return_s = pd.Series(data_daily_return)
        strat_dict['emaperiod'].append(strat.p._getkwargs()['emaperiod'])
        strat_dict['upper'].append(strat.p._getkwargs()['upper'])
        strat_dict['lower'].append(strat.p._getkwargs()['lower'])
        strat_sortino = emp.sortino_ratio(strat_daily_return_s, required_return = 0.01 / 365, period='daily')
        strat_dict['策略年化索丁诺比率'].append(strat_sortino)
        btc_sortino = emp.sortino_ratio(data_daily_return_s, required_return = 0.01 / 365, period='daily')
        strat_dict['比特币年化索丁诺比率'].append(btc_sortino)
        print(strat.p._getkwargs(), '年化索丁诺比率:策略:', strat_sortino, '比特币:', btc_sortino)
    strat_df = pd.DataFrame(strat_dict)
    strat_best = strat_df[strat_df['策略年化索丁诺比率'] == strat_df['策略年化索丁诺比率'].max()]
    print('最优的emaperiod:', strat_best.loc[0, 'emaperiod'], '最优的upper:', strat_best.loc[0, 'upper'], '最优的lower:', strat_best.loc[0, 'lower'], '最大的策略年化索丁诺比率:', strat_best.loc[0, '策略年化索丁诺比率'], '比特币年化索丁诺比率:', strat_best.loc[0, '比特币年化索丁诺比率'])
    print('==================================================')
    '''
    pyfoliozer = strat0.analyzers.getbyname('pyfolio')
    returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
    transactions.rename(columns = {'value': 'txn_volume'}, inplace = True)
    transactions.insert(1, 'txn_shares', transactions['amount'])
    
    
    benchmark = pd.read_csv('benchmark201806-201905.csv')
    benchmark['Datetime'] = pd.to_datetime(benchmark['Date'],format="%Y-%m-%d")
    benchmark_index = benchmark.set_index(['Datetime'])
    benchmark_rets = diff(benchmark_index['Close']) / benchmark_index['Close'][:-1]
    benchmark_rets.rename('SPY', inplace = True) 
    benchmark_rets = benchmark_rets.tz_localize('UTC')
    pf.create_returns_tear_sheet(returns, benchmark_rets = benchmark_rets, live_start_date='2019-04-01')
    
    pf.create_round_trip_tear_sheet(returns, positions, transactions)
    '''
    end = time.process_time()
    t=end-start
    print("运行时间为：（秒）",t) 
